﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MapEngine;

namespace Dark_Dungeon
{
    [Serializable()]
    public abstract class Entity : ISerializable
    {
        static public UInt32 Entities = 0;

        protected UInt32 ID;
        protected EntityGraphics graphics;
        protected Vector2 position;
        protected EntityDirection direction;
        protected EntityState state;

        protected bool Alive = true;

        protected int HitPoints = 5;
        protected int Air = 10;  // Aire con el que nadar.

        public string Name;

        #region -- Consturctores --
        public Entity(Vector2 position)
        {
            ID = Entities++;
            this.position = position;
            this.direction = EntityDirection.Down;
        }
        #endregion

        public void setID(UInt32 ID)
        {
            this.ID = ID;
        }


        #region -- Update --
        public virtual void Update(GameTime gameTime, TileMap map)
        {
            if (HitPoints > 0)
                map.setOccupied(position, true);
            else
            {
                Alive = false;
                map.setOccupied(position, false);
                Debug.AddStaticText(this.Name + " " + "muere.");
            }

        }
        #endregion

        #region -- Draw --
        public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {

            if (Resources.Textures[graphics.texture] == null) return;

            Vector2 dst = new Vector2(position.X * MapConfig.TileSize.X, position.Y * MapConfig.TileSize.Y);
            spriteBatch.Draw(
                    Resources.Textures[graphics.texture],
                    dst,
                    new Rectangle(graphics.tile.X * 32, graphics.tile.Y * 32, 32, 32),
                    Color.White,
                    0.0f,
                    Vector2.Zero,
                    1.0f,
                    SpriteEffects.None,
                    Config.SpriteLayer);

            //vida del pj
            spriteBatch.DrawString(Resources.TerminalFont, this.HitPoints.ToString(), new Vector2(dst.X-32, dst.Y - 20), Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 1.0f);
            spriteBatch.DrawString(Resources.TerminalFont, this.Air.ToString(), new Vector2(dst.X + 32, dst.Y - 20), Color.LightBlue, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 1.0f);

        }
        #endregion


        #region -- Movimiento --
        protected void Move(TileMap map, EntityDirection dir)
        {
            // Si no hay ninguna dirección a la que moverse, no hace nada
            if (dir == EntityDirection.NoDirection)
                return;

            // Antes de moverse, aplica los efectos de la celda actual
            switch (map.GetMaterial(position))
            {
                    //25% de probabilidad de quedar pegado a la casilla actual
                case TileMaterial.Glue:
                case TileMaterial.Hole:
                    Random rand = new Random();
                    if (rand.Next(100) < 25)
                    {
                        Console.WriteLine("Pegado");
                        return;
                    }
                    break;
            }

            // Calcula la nueva posición
            Vector2 newPosition = Extensions.CalculateNewPosition(dir, position);

            // Determina si se puede mover en esa dirección
            if (map.getOccupied(newPosition))
                return;

            // Quitar el estado ocupado de la posición anterior
            map.setOccupied(position, false);

            // Aplica los efectos de la celda nueva
            if (map.GetMaterial(newPosition) != TileMaterial.Wall)
            {
                switch (map.GetMaterial(newPosition))
                {
                    //25% de probabilidad de quedar pegado a la casilla actual
                    case TileMaterial.Ice:
                        Random rand = new Random();
                        if (rand.Next(100) < 25)
                        {
                            Console.WriteLine("Resbalado");
                            newPosition = Extensions.CalculateNewPosition(dir, newPosition);
                        }
                        break;

                    //Cae en un agujero hace 5 de daño
                    case TileMaterial.Hole:
                        TakeDirectDamage(5);
                        break;

                    //Lava, mucho daño, 20 de daño
                    case TileMaterial.Fire:
                        TakeDirectDamage(20);
                        break;

                    //Agua: Nada hasta quedarse sin aire, despues va perdiendo 5 puntos por movimiento
                    case TileMaterial.Water:
                        if (this.Air > 0)
                            AirConsumption(1);
                        else
                            TakeDirectDamage(5);
                        break;

                    //Casilla normal: Recupera oxigeno en caso de no haberlo perdido.
                    default:
                        if (this.Air < 10)
                            AirRestore(5);
                        break;
                }

                position = newPosition;
            }

            map.setOccupied(position, true);
        }
        #endregion

        #region -- Otros Métodos --

        /// <summary>
        /// Recibe daño.
        /// </summary>
        /// <param name="attack">Daño recibido.</param>
        public int TakeDirectDamage(int attack)
        {
                HitPoints -= attack;
                return attack;
        }

        /// <summary>
        /// Consume aire cuando no se puede respirar.
        /// </summary>
        /// <param name="air">Cantidad de aire.</param>
        protected void AirConsumption(int air)
        {
            this.Air -= air;
        }

        /// <summary>
        /// Recuperar aire.
        /// </summary>
        /// <param name="air">Cantidad de aire.</param>
        protected void AirRestore(int air)
        {
            this.Air += air;
            if (this.Air > 10)
                this.Air = 10;
        }

        #endregion

        #region -- Porpiedades --
        public Vector2 getPosition()
        {
            return position;
        }

        public bool IsAlive()
        {
            return Alive;
        }
        #endregion

        #region -- Serialización/Deserialización --

        //Deserialización
        public Entity(SerializationInfo info, StreamingContext ctxt)
        {
            ID = (UInt32)info.GetValue("ID", typeof(UInt32));
            graphics = (EntityGraphics)info.GetValue("graphics", typeof(EntityGraphics));
            position = (Vector2)info.GetValue("position", typeof(Vector2));
            direction = (EntityDirection)info.GetValue("direction", typeof(EntityDirection));
            state = (EntityState)info.GetValue("state", typeof(EntityState));

            Alive = (bool)info.GetValue("Alive", typeof(bool));

            HitPoints = (int)info.GetValue("HitPoints", typeof(int));
            Air = (int)info.GetValue("Air", typeof(int));

            Name = (string)info.GetValue("Name", typeof(string));
        }

        //Serialización
        public virtual void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("ID", ID);
            info.AddValue("graphics", graphics);
            info.AddValue("position", position);
            info.AddValue("direction", direction);
            info.AddValue("state", state);
            info.AddValue("Alive", Alive);
            info.AddValue("HitPoints", HitPoints);
            info.AddValue("Air", Air);
            info.AddValue("Name", Name);
        }
        #endregion
    }




    public enum EntityState
    {
        Normal, Submerged
    }



    public enum EntityDirection
    {
        NoDirection = 0, Up, Down, Left, Right
    }



    [Serializable()]
    public struct EntityGraphics : ISerializable
    {
        public EntityTexture texture;
        public Point tile;

        public EntityGraphics(SerializationInfo info, StreamingContext ctxt)
        {
            texture = (EntityTexture)info.GetValue("texture", typeof(EntityTexture));
            tile = (Point)info.GetValue("tile", typeof(Point));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("texture", texture);
            info.AddValue("tile", tile);
        }
    }



    public static class Extensions
    {
        public static Point vector(this EntityDirection dir)
        {
            switch (dir)
            {
                case EntityDirection.Up: return new Point(0, -1);
                case EntityDirection.Down: return new Point(0, +1);
                case EntityDirection.Left: return new Point(-1, 0);
                case EntityDirection.Right: return new Point(+1, 0);
                default: return new Point(0, 0);
            }
        }


        /// <summary>
        /// Calcula una nueva posición segun la dirección.
        /// </summary>
        /// <param name="dir">Dirección a donde mover.</param>
        /// <param name="oldposition">Posición de la que parte el movimiento.</param>
        /// <returns>La nueva posición calculada.</returns>
        public static Vector2 CalculateNewPosition(EntityDirection dir, Vector2 oldposition)
        {
            Point PntDir = Extensions.vector(dir);
            return new Vector2(oldposition.X + PntDir.X, oldposition.Y + PntDir.Y);
        }

    }
}
